跳到主要内容

指标采集与 Exporter

前面的文章把 Prometheus 装起来了,但 Prometheus 自己不生产指标——它只负责「拉」。指标从哪来?除了 K8s 组件自带的 /metrics 端点,绝大多数来自 Exporter 和应用自己的埋点。这篇笔记记录 Exporter 的定位与生态、node_exporter 的完整部署流程、scrape_config 抓取配置、指标暴露文本格式、给应用埋点的最小实践,以及 blackbox_exporter 的外部探测玩法。示例环境:Prometheus 3.x(2.5x 同样适用),node_exporter 1.x,虚拟机以 Ubuntu 22.04 为例,K8s 场景为 1.30 集群。

Exporter 是什么​

Prometheus 拉数据的前提,是目标能吐出一口标准格式的 /metrics。自家应用可以埋点直接暴露(见后文),但操作系统、MySQL、Redis 这些第三方系统不会为 Prometheus 改造,于是就有了 Exporter:一个翻译层。它主动去向目标系统要数据——读 /proc、查 MySQL 状态变量、调 API——再把结果翻译成 Prometheus 文本格式暴露出来,等 Prometheus 来抓。

日常会打交道的几个:

Exporter监控对象一句话说明
node_exporterLinux 主机CPU、内存、磁盘、网络、文件系统,装机标配
mysqld_exporterMySQL / MariaDB连接数、慢查询、主从复制延迟等内部状态
redis_exporterRedis命中率、内存碎片、keyspace、慢日志
blackbox_exporter任意网络目标从外面探测 HTTP/TCP/ICMP/DNS,回答「服务通不通、证书快到期没」
kube-state-metricsK8s 对象把 Deployment、Pod 等对象的期望与实际状态变成指标
cAdvisor容器每个容器的资源用量,已内嵌在 kubelet 里,无需单独安装
Exporter 不是越全越好

每个 Exporter 都是一个要部署、要升级、要监控的组件。先把 node_exporter 和业务自身的指标抓稳,再按需加数据库、中间件的 Exporter。选型优先官方或大厂维护的,名单见 prometheus.io 官方 Exporter 列表,野生的慎重上生产。

node_exporter 实战​

以 Ubuntu 22.04 二进制部署为例。K8s 集群里 node_exporter 一般随 kube-prometheus-stack 以 DaemonSet 装好(见 监控、日志与可观测性),集群外虚拟机才需要手工装。

安装二进制并注册 systemd​

# 版本号按 GitHub 最新 release 替换
cd /usr/local/src
curl -LO https://github.com/prometheus/node_exporter/releases/download/v1.9.1/node_exporter-1.9.1.linux-amd64.tar.gz
tar xf node_exporter-1.9.1.linux-amd64.tar.gz
install -m 0755 node_exporter-1.9.1.linux-amd64/node_exporter /usr/local/bin/

# 专用系统账号,不给家目录和登录 shell
useradd --system --no-create-home --shell /usr/sbin/nologin node_exporter

systemd 单元文件,完整可照抄:

# /etc/systemd/system/node_exporter.service
[Unit]
Description=Prometheus Node Exporter
Documentation=https://prometheus.io/docs/guides/node-exporter/
Wants=network-online.target
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter \
--web.listen-address=:9100 \
--collector.systemd \
--no-collector.netstat \
--collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run|var/lib/docker/.+)($|/)
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target

启动并验证:

systemctl daemon-reload
systemctl enable --now node_exporter
systemctl status node_exporter --no-pager
curl -s localhost:9100/metrics | head -20

collector 开关​

node_exporter 的指标按 collector 分组:--collector.xxx 打开、--no-collector.xxx 关闭、--collector.disable-defaults 全关后再按需白名单。默认那组(cpu、meminfo、diskstats、netdev、filesystem 等)够用且开销小,真正要权衡的是这几个:

collector默认状态说明
systemd关systemd unit 状态;机器上 unit 很多时抓取会变慢,按需开
processes关遍历 /proc 统计每个进程,进程多的机器上 CPU 开销明显
perf关硬件性能计数器,开销最大的一类,排查性能问题时临时开
netstat开/proc/net/netstat 全量 SNMP 指标,量大且多数用不上,建议关
textfile开读指定目录下的 .prom 文件,自定义脚本报指标的通道
textfile collector 是老朋友

定时任务的结果(上次备份是否成功、证书还有几天到期)没有常驻进程可埋点,常见做法是 cron 脚本把结果写成 .prom 文件,丢进 --collector.textfile.directory 指定的目录,node_exporter 抓取时顺带暴露。写入要原子化:先写临时文件再 mv,别让 Prometheus 抓到半个文件。

加进抓取目标​

在 prometheus.yml 的 scrape_configs 里加一个 job:

scrape_configs:
- job_name: node
scrape_interval: 15s
static_configs:
- targets:
- 10.0.1.11:9100
- 10.0.1.12:9100
labels:
env: prod
role: vm

改完配置记得 reload(姿势见 部署与快速上手)。十几秒后看 Web UI 的 Status → Targets,node job 下的目标应该是 UP;再跑一条 node_load1 验证数据真的进来了。

抓取配置详解 scrape_config​

把上面那个 job 拆开来逐项看:

scrape_configs:
- job_name: node # 任务名,自动变成所有样本的 job 标签
metrics_path: /metrics # 抓取路径,默认就是 /metrics
scheme: http # http/https,https 要另配 tls_config
scrape_interval: 15s # 本 job 抓取间隔,覆盖 global.scrape_interval
scrape_timeout: 10s # 单次抓取超时,必须 <= scrape_interval
static_configs: # 静态目标清单
- targets:
- 10.0.1.11:9100
- 10.0.1.12:9100
labels: # 这一组 targets 统一打的标签
env: prod

几个要点:

  • job_name 会变成所有样本的 job 标签。同一 job 下可以有多个 static_configs 分组,每组各自打不同的 labels。
  • labels 打上的标签是时序身份的一部分:node_load1{job="node", env="prod", instance="10.0.1.11:9100"} 和改了任何一个标签值的序列,是两条互不相干的时序。给一组机器改 env 标签,老曲线就地断掉,想清楚再打。
  • scrape_timeout 不写时继承 global 里的值(默认 10s)。Prometheus 启动和 reload 时都会校验 timeout ≤ interval,违反直接拒载配置。
  • instance 标签默认取 target 的地址,可以在 labels 里覆盖成好认的名字(虚拟机场合实用)。
honor_labels

如果目标暴露的指标本身就带了 job/instance 标签(典型场景:抓 Pushgateway、抓另一台 Prometheus 做联邦),默认会被 Prometheus 侧的标签覆盖掉;设 honor_labels: true 则保留目标侧原标签。日常抓 Exporter 用不到,知道有这回事即可。

指标暴露格式​

curl 任意一个 /metrics,看到的都是同一种文本格式。逐行拆解:

# HELP node_load1 1m load average.
# TYPE node_load1 gauge
node_load1 0.42
# HELP node_cpu_seconds_total Seconds the CPUs spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 1837421.12
node_cpu_seconds_total{cpu="0",mode="system"} 1204.56
  • # HELP:指标的说明文字,一个指标一行。
  • # TYPE:指标类型,counter / gauge / histogram / summary 四选一。
  • 其余行是样本:指标名{标签="值",...} 数值 [可选时间戳]。没有标签时大括号整个省略;时间戳一般不给,Prometheus 用抓取时刻代替。

四种类型在暴露格式里的长相——counter 和 gauge 上面已经见过(counter 约定名字以 _total 结尾,只增不减;gauge 可上可下)。histogram 是「一族」指标:_bucket 按 le 分桶累计计数,外加 _sum 与 _count:

# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 24054
http_request_duration_seconds_bucket{le="0.5"} 33444
http_request_duration_seconds_bucket{le="+Inf"} 33920
http_request_duration_seconds_sum 534.23
http_request_duration_seconds_count 33920

summary 类似,但直接给客户端算好的分位数,缺点是无法再跨实例聚合:

# TYPE rpc_duration_seconds summary
rpc_duration_seconds{quantile="0.5"} 0.031
rpc_duration_seconds{quantile="0.9"} 0.084
rpc_duration_seconds_sum 175.73
rpc_duration_seconds_count 2693

四种类型的语义和「histogram 还是 summary」的取舍,在 核心概念与架构 里已经讲过,这里认得出格式即可。

给自己的应用加指标​

第三方系统用 Exporter,自己的应用就直接埋点——官方给 Go/Java/Python/Ruby 等维护了 client library:在代码里定义指标、自增计数,库自带 HTTP handler 负责吐 /metrics。Python 最小例子:

# pip install prometheus_client
import random
import time

from prometheus_client import Counter, Gauge, start_http_server

REQUESTS = Counter("demo_requests_total", "进程收到的请求总数")
INFLIGHT = Gauge("demo_inflight_requests", "正在处理中的请求数")


def handle_request():
REQUESTS.inc() # 计数 +1
INFLIGHT.inc() # 在途 +1
try:
time.sleep(random.random()) # 假装在干活
finally:
INFLIGHT.dec() # 干完 -1,finally 保证异常也减回去


if __name__ == "__main__":
start_http_server(8000) # 在 :8000/metrics 暴露指标
while True:
handle_request()

跑起来后 curl localhost:8000/metrics 就能看到 demo_requests_total 等指标,照前文加进 scrape_configs 即可被抓取。真实项目里通常把这个 handler 挂到应用已有的 HTTP 服务上(Flask/Django 都有现成集成),不必单开端口。

标签基数是生命线

埋点管住手:标签的值域必须有限(状态码、HTTP 方法、路径模板)。把 user_id、request_id、完整 URL 这类高基数值做成标签,时序数量会爆炸,先把 Prometheus 拖垮的就是它。要排查个体请求请用日志和 tracing,见 监控、日志与可观测性。

短任务怎么办:Pushgateway​

批处理、备份脚本、CronJob 这类几秒就跑完的短任务,Prometheus 大概率来不及抓。这时让任务干完主动把结果推给 Pushgateway 中转,Prometheus 再去抓 Pushgateway:

# 任务结束时推一条「上次成功时间」
echo "backup_last_success_timestamp_seconds $(date +%s)" \
| curl --data-binary @- http://pushgateway:9091/metrics/job/backup/instance/db1
Pushgateway 不要滥用

它是短任务的兜底,不是常驻服务的替代方案:走 push 之后目标挂了 up 也不会掉(数据停在最后一次推送),pull 模型天然的健康探活就丢了;多实例负载均衡的服务也没法用(推上来的数据分不清是谁的)。能拉就拉,实在拉不到才推。

blackbox_exporter 探测​

前面的 Exporter 都从「里面」看系统,blackbox_exporter 反过来:站在用户角度从「外面」探测——URL 返回什么状态码、证书还有多久到期、TCP 端口通不通、机器 ping 得通吗。

先写探测模块配置:

# blackbox.yml
modules:
http_2xx: # HTTP 探测:2xx/3xx 算成功
prober: http
timeout: 5s
http:
method: GET
follow_redirects: true
preferred_ip_protocol: ip4
tcp_connect: # TCP 探测:能建立连接就算成功
prober: tcp
timeout: 5s
icmp: # ICMP 探测:ping
prober: icmp
timeout: 5s
icmp:
preferred_ip_protocol: ip4

Prometheus 侧的玩法比较绕:探测目标不是 target,而是参数。真正去连的地址被 relabel 改写成 blackbox_exporter 自己。HTTP 探测:

scrape_configs:
- job_name: blackbox-http
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://www.example.com
- https://api.example.com/healthz
relabel_configs:
- source_labels: [__address__] # 真实探测目标写进 target 参数
target_label: __param_target
- source_labels: [__param_target] # instance 标签显示成探测目标
target_label: instance
- target_label: __address__ # 实际去连 blackbox_exporter
replacement: blackbox:9115

TCP 探测——换 module 和 targets,relabel 三板斧原样照搬:

  - job_name: blackbox-tcp
metrics_path: /probe
params:
module: [tcp_connect]
static_configs:
- targets:
- 10.0.2.11:5432 # PostgreSQL
- 10.0.3.11:6379 # Redis
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox:9115

ICMP 探测同理:

  - job_name: blackbox-icmp
metrics_path: /probe
params:
module: [icmp]
static_configs:
- targets:
- 10.0.1.1 # 网关
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox:9115

探测结果看 probe_success(1 通 0 不通)和 probe_duration_seconds;HTTP 探测还附送证书到期时间戳 probe_ssl_earliest_cert_expiry,配告警很好用:

probe_success{job="blackbox-http"} == 0
ICMP 需要特权

发 ICMP 包要原始套接字权限。二进制部署用 setcap cap_net_raw=+ep /usr/local/bin/blackbox_exporter 授权;容器里跑要给 Pod 加 NET_RAW capability,否则 icmp 模块会一直探测失败。

常见坑​

  • 端口被防火墙/安全组拦住:Targets 页报 connection refused 或 i/o timeout。先 curl 一遍目标的 /metrics,不通就去放通端口——云主机看安全组,K8s 里看 NetworkPolicy。
  • scrape_timeout 大于 scrape_interval:配置直接拒载,日志报 "scrape timeout greater than scrape interval"。抓得慢的大 Exporter 要把两个值一起调大,而不是只调 timeout。
  • 机器时间漂移:Exporter 所在主机时钟跑偏,轻则曲线锯齿、TLS 证书校验莫名失败,重则告警评估混乱。所有机器统一上 chrony/NTP,别手改时间。
  • metrics 路径不对 404:target 状态 DOWN,报 server returned HTTP status 404。有的应用把指标挂在 /actuator/prometheus、/api/metrics 这类路径上,metrics_path 改对即可。
  • Pushgateway 旧数据清不掉:任务的 job/instance 组合改了名之后,旧组合的数据会一直留在 Pushgateway 里被继续抓。换名字前清掉旧的:curl -X DELETE http://pushgateway:9091/metrics/job/backup/instance/db1。

小结​

  • Exporter 是翻译层:第三方系统 → Prometheus 格式。先把 node_exporter 抓稳,再按需加数据库、中间件 Exporter。
  • node_exporter 默认 collector 够用;processes、perf 这类重 collector 按需再开;textfile 是自定义脚本报指标的通道。
  • scrape_config 核心字段:job_name、metrics_path、scheme、scrape_interval/timeout、static_configs;labels 打上就是时序身份,想清楚再打。
  • /metrics 文本格式就是 HELP/TYPE/样本行三件套;自己埋点用官方 client library,短任务交给 Pushgateway 兜底。
  • blackbox_exporter 从外部探测,记住「探测目标是参数、连接地址是 blackbox」这套 relabel 三板斧。
  • 机器一多,static_configs 就维护不动了——下一篇讲服务发现与 relabeling。